package com.space.components.domain.core.diff;

import com.space.components.domain.core.Entity;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * @author <a href="mailto:414563035@qq.com">Bedroom</a>
 * @date 2021/5/26 3:12 下午
 */
public class ArrayDiff<T> extends ArrayList<ItemDiff<T>> {
    private Consumer<T> added;
    private Consumer<T> removed;
    private Consumer<T> modified;

    public ArrayDiff(Object entityArray, Object snapshotArray, Class<T> listType) {
        if (entityArray == null && snapshotArray == null) {
            return;
        }
        final int entityLen = getLength(entityArray);
        final int snapshotLen = getLength(snapshotArray);
        if (Entity.class.isAssignableFrom(listType)) {
            if (entityLen == 0) {
                for (int i = 0; i < snapshotLen; i++) {
                    this.add(new ItemDiff<>(DiffType.Removed, null, (T) Array.get(snapshotArray, i)));
                }
            } else {
                final Map<Object, Entity<?>> snapshotMap = new HashMap<>();
                for (int i = 0; i < snapshotLen; i++) {
                    final Entity<?> snapshot = (Entity<?>) Array.get(snapshotArray, i);
                    snapshotMap.put(snapshot.getIdentifier().getId(), snapshot);
                }
                for (int i = 0; i < entityLen; i++) {
                    final Entity<?> entity = (Entity<?>) Array.get(entityArray, i);
                    final Entity<?> snapshot = snapshotMap.get(entity.getIdentifier().getId());
                    if (Objects.nonNull(snapshot)) {
                        final EntityDiff<?> entityDiff = EntityDiffUtils.diff(entity, snapshot);
                        if (entityDiff.isSelfModified()) {
                            this.add(new ItemDiff<>(DiffType.Modified, (T) entity, (T) snapshot, (T) entityDiff.toSelfDiffEntity()));
                        }
                        snapshotMap.remove(entity.getIdentifier().getId());
                    } else {
                        this.add(new ItemDiff<>(DiffType.Added, (T) entity, null));
                    }
                }
                snapshotMap.forEach((k, v) -> {
                    this.add(new ItemDiff<>(DiffType.Removed, null, (T) v));
                });
            }
        } else {
            throw new RuntimeException("未想到这种情况的实际应用, 请联系开发者描述一下使用场景");
//            if (entityLen == 0) {
//                for (int i = 0; i < snapshotLen; i++) {
//                    this.add(new ItemDiff(DiffType.Removed, null, Array.get(snapshotArray, i)));
//                }
//            } else {

//                final List<Object> snapshotList = new ArrayList<>();
//                for (int i = 0; i < snapshotLen; i++) {
//                    final Object snapshot = Array.get(snapshotArray, i);
//                    snapshotList.add(snapshot);
//                }
//
//                for (int i = 0; i < entityLen; i++) {
//                    final Object entity = Array.get(entityArray, i);
//                    if (snapshotList.contains(entity)) {
//                        this.add(new ItemDiff(DiffType.Modified, entity, snapshot));
//                        snapshotList.remove(entity);
//                    } else {
//                        this.add(new ItemDiff(DiffType.Added, entity, null));
//                    }
//
//                    snapshotList.forEach(v -> this.add(new ItemDiff(DiffType.Removed, null, v)));
//                }
//            }
        }
    }

    private int getLength(Object array) {
        if (array == null) {
            return 0;
        }
        return Array.getLength(array);
    }

    public ArrayDiff<T> added(Consumer<T> added) {
        this.added = added;
        return this;
    }

    public ArrayDiff<T> removed(Consumer<T> removed) {
        this.removed = removed;
        return this;
    }

    public ArrayDiff<T> modified(Consumer<T> modified) {
        this.modified = modified;
        return this;
    }

    public void execute() {
        consume(this.added, this.removed, this.modified);
    }

    public void consume(Consumer<T> added, Consumer<T> removed, Consumer<T> modified) {
        this.forEach(itemDiff -> {
            switch (itemDiff.getType()) {
                case Added:
                    if (added != null) {
                        added.accept(itemDiff.getValue());
                    }
                    break;
                case Removed:
                    if (removed != null) {
                        removed.accept(itemDiff.getValue());
                    }
                    break;
                case Modified:
                    if (modified != null) {
                        modified.accept(itemDiff.getValue());
                    }
                    break;
                default:
            }
        });
    }
}
